#pragma once
#include <iostream>
#include <unistd.h>
#include "Socket.hpp"
#include "Log.hpp"
#include <sys/epoll.h>
using namespace SocketModule;
using namespace LogModule;

// 暂不做过多解耦
class EpollServer
{
    const static int size = 4096;    // 定义一个数组容量的大小
    const static int defaultfd = -1; // 文件描述符的缺省值
public:
    EpollServer(int port)                            // 传入端口号构建listen套接字
        : _listensock(std::make_unique<TcpSocket>()) // 创建一个TCP套接字
          ,
          _isrunning(false)
    {
        // 1、创建listensocket
        _listensock->BuildTcpSocketMethod(port); // 初始化listen套接字   3

        // 2、创建epoll模型
        _epfd = epoll_create(256); // 使用 epoll_create 函数创建一个 epoll 实例，参数 256 表示可以监听的文件描述符数量
        if (_epfd < 0)             // 说明创建epoll模型 失败了
        {
            LOG(LogLevel::FATAL) << "epoll create error";
            exit(EPOLL_CREATE_ERR);
        }

        // 到这里说明epoll模型创建成功了,这里进行日志的输出操作
        LOG(LogLevel::INFO) << "epoll_create success" << _epfd; //  我们先创建的是listensocket是3，那么我们这个也是文件描述符，那么就是4

        // 3、将listensocket设置到内核中
        // 定义 epoll_event 结构体变量 ev，用于描述要监听的事件。
        struct epoll_event ev;          // 没有设置到内核中，没有在红黑树中新增节点
        ev.events = EPOLLIN;            // 设置 ev.events 为 EPOLLIN，表示要监听监听套接字上的读事件
        
        
        ev.data.fd = _listensock->Fd(); // 设置 ev.data.fd 为监听套接字的文件描述符，以便在 epoll 检测到事件时，能够知道是哪个文件描述符发生了事件。
        //这里给 ev.data.fd 赋值监听套接字的文件描述符，是为了后续事件处理时，能明确区分出 “新连接请求事件” 和 “已连接套接字的数据收发事件”，
        //是实现 “识别不同类型事件并执行对应逻辑” 的基础。


        // 使用 epoll_ctl 函数将监听套接字添加到 epoll 实例中，EPOLL_CTL_ADD 表示添加操作。
        int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock->Fd(), &ev); // 这个时候我们就向红黑树中新增了节点
        // 让操作系统关心_listensock上的读事件
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "add listensockfd failed";
            exit(EPOLL_CTL_ERR);
        }
    }
    // 到这里就有了一个listen套接字了
    void Start() // 启动监听
    {
        int timeout = 1000;
        _isrunning = true; // 运行中，开始监听
        while (_isrunning)
        {
            // 不能直接accept呢？
            // 从_epfd中获取已经就绪的文件描述符了
            // 就绪的文件描述符存放于_revs
            // 大小是size
            int n = epoll_wait(_epfd, _revs, size, timeout);//用于等待并处理就绪事件的部分。
            switch (n)
            {
            case 0://超时了
                LOG(LogLevel::DEBUG)<<"timeout";
                break;
            case -1:
                LOG(LogLevel::ERROR)<<"epoll error";
                break;
            default:
                //到这里就都是就绪了的
                Dispatcher(n);//n就是有几个就绪了
                break;
            }
        }
        _isrunning = false; // 运行结束，监听结束
    }
    // 处理就绪的事件
    void Dispatcher(int rnum)
    {
        LOG(LogLevel::DEBUG)<<"event ready...";
        //到这里，所有的就绪事件和文件描述符都在revs中
        for(int i=0;i<rnum;i++)
        {
            int sockfd=_revs[i].data.fd;
            uint32_t revent=_revs[i].events;
            //epoll也要循环处理就绪事件---这是应该的，本来就有可能多个fd就绪
            if(revent&EPOLLIN)
            {
                //读事件就绪
                
                //你怎么知道是listensockfd就绪了，还是普通套接字就绪了
                if(sockfd==_listensock->Fd())
                {
                    //走到这里肯定是读事件就绪了，并且是新链接到来了
                    Accepter();
                }
                else 
                {
                    //走到 这里是读事件就绪，并且是普通套接字可读
                    Recver(sockfd);//对这个事件进行一个读取操作
                }
            }
            // if(_revs[i].events&EPOLLOUT)
            // {
            //     //写事件就绪
            // }
        }
    }
    //获取新链接,链接管理器
    void Accepter()
    {

        //获取新链接
        InetAddr client;//定义客户端信息
        //至少有一个新链接到来，accept一次是绝对不会阻塞的
        int sockfd=_listensock->Accept(&client);//这里的accrpt是绝对不会阻塞的
        //获得对应的链接，这里的accept不会阻塞的

        //这里是不会阻塞的，因为上层的listensock已经就绪了，将等的过程和拷的过程分离了
        if(sockfd>=0)
        {
            //获取新链接成功到来,我们能否直接read/recv()
            //fd是否读继续，我们是不清楚的
            //只有谁最清楚，未来sockfd上是否有事件就绪----select
            //将新的sockefd，托管给select
            //如何托管？将新的fd放入到辅助数组中即可
            LOG(LogLevel::INFO)<<"get a new link ,sockfd:"<<sockfd<<"client is"<<client.StringAddr();

            //能不能直接recv呢？不能！
            //只有epoll知道文件描述符是否就绪了，只有就绪了，才能recv

            //将新的sockfd添加到内核
            struct epoll_event ev;
            ev.events=EPOLLIN;
            ev.data.fd=sockfd;
            int n=epoll_ctl(_epfd,EPOLL_CTL_ADD,sockfd,&ev);//将新链接向内核中添加
            if (n < 0)//新链接获取失败了
            {
                LOG(LogLevel::WARNING) << "add listensockfd failed";
            }
            else//否则的话打印日志
            {
                LOG(LogLevel::INFO) << "epoll_ctl add sockfd success"<<sockfd;
            }

        }
    }
    //IO处理器
    void Recver(int sockfd)
    {
        char buffer[1024];
        //我在这里读取的时候，会不会阻塞，本次读取是不会被阻塞的
        ssize_t n=recv(sockfd,buffer,sizeof(buffer)-1,0);//从fd中读取，放到buffer中，最后一个参数设置为0，默认阻塞等待
        if(n>0)//读取成功了
        {
            buffer[n]=0;
            cout<<"client say@"<<buffer<<endl;
        }
        else if(n==0)//代表客户端就退出了
        {
            //关闭链接的同时也要移除对该文件描述符的关心
            //从epoll中移除fd的关心&&关闭fd  epoll_ctl只能移除合法的fd,先移除，再关闭
            int m=epoll_ctl(_epfd,EPOLL_CTL_DEL,sockfd,nullptr);
            if(m>0)
            {
                //移除成功了
                LOG(LogLevel::INFO) << "epoll_ctl remove sockfd success"<<sockfd;
            }
            close(sockfd);


        }
        else //读出错了
        {
            LOG(LogLevel::ERROR)<<"recv error";
            //关闭链接的同时也要移除对该文件描述符的关心
            //从epoll中移除fd的关心&&关闭fd  epoll_ctl只能移除合法的fd,先移除，再关闭
            int ret=epoll_ctl(_epfd,EPOLL_CTL_DEL,sockfd,nullptr);
            if(ret>0)
            {
                //移除成功了
                LOG(LogLevel::INFO) << "epoll_ctl remove sockfd success"<<sockfd;
            }
            close(sockfd);
        }

    }

    void Stop() // 停止监听
    {
        _isrunning = false; // 停止监听
    }
    ~EpollServer()
    {
        _listensock->Close();
        if (_epfd > 0)
            close(_epfd); // 关闭这个文件描述符
    }

private:
    std::unique_ptr<Socket> _listensock; // 创建一个基类套接字
    bool _isrunning = false;
    int _epfd;

    struct epoll_event _revs[size]; // 用于存储 epoll 等待（epoll_wait）操作返回的就绪事件。
};